#include <stdlib.h>
#include <gtk/gtk.h>

//using namespace std;

typedef struct {
    GtkEntry* m[3][16];
    GtkWidget* l[3];
}MATRICES;

static void createIdentityMatrix(GtkWidget *table, MATRICES* mats, gint index)
{
    gint i = 0;
    gint j = 0;
    GtkWidget *entry = NULL;

    for (; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            entry = gtk_entry_new ();
            mats->m[index][j + i*4] = GTK_ENTRY (entry);
            gtk_entry_set_max_length (GTK_ENTRY (entry), 7);
            gtk_entry_set_width_chars(GTK_ENTRY (entry), 7);
            if (i != j) {
                char str[10];
                float flt = (j+i*4.0);
                sprintf(str, "%.7g", flt );
                gtk_entry_set_text (GTK_ENTRY (entry), str);
            }
            else{
                gtk_entry_set_text (GTK_ENTRY (entry), "1.0");
            }
            gtk_table_attach (GTK_TABLE (table), entry, j, j + 1, i, i + 1, GTK_SHRINK, GTK_SHRINK, 0 ,0);
        }
    }

}

static void getFirstTwoMatrice(float(* m)[16], gpointer data)
{
    gint ri = 0;
    gint rj = 0;
    gint rk = 0;
    for (; rk < 2; rk++) {
        for (ri = 0; ri < 4; ri++) {
            for (rj = 0; rj < 4; rj++) {
                const gchar *temp;
                GtkEntry* entry;
                entry = ((MATRICES*)data)->m[rk][ri * 4 + rj];
                temp = gtk_entry_get_text(entry);
                float a = strtof(temp, NULL);
                m[rk][ri * 4 + rj] = a;
            }
        }
    }
}

static void putResultIntoTable(float* r, gpointer data)
{
    gint rj = 0;
    gint ri = 0;
    for (; ri < 4; ri++) {
        for (rj = 0; rj < 4; rj++) {
            char str[10];
            sprintf(str, "%.7g", r[ri * 4 + rj] );
            GtkEntry* entry;
            entry = ((MATRICES*)data)->m[2][ri * 4 + rj];
            gtk_entry_set_text(entry, str);
        }
    }
}

static void multiply22 (float* fOut_11, float* fOut_12, float* fOut_21, float* fOut_22,
					float f1_11, float f1_12, float f1_21, float f1_22,
					float f2_11, float f2_12, float f2_21, float f2_22)
{
    const float x1 = ((f1_11 + f1_22) * (f2_11 + f2_22));
	const float x2 = ((f1_21 + f1_22) * f2_11);
	const float x3 = (f1_11 * (f2_12 - f2_22));
	const float x4 = (f1_22 * (f2_21 - f2_11));
	const float x5 = ((f1_11 + f1_12) * f2_22);
	const float x6 = ((f1_21 - f1_11) * (f2_11 + f2_12));
	const float x7 = ((f1_12 - f1_22) * (f2_21 + f2_22));

	*fOut_11 = x1 + x4 - x5 + x7;
	*fOut_12 = x3 + x5;
	*fOut_21 = x2 + x4;
	*fOut_22 = x1 - x2 + x3 + x6;
}

static void optimized(float(* m)[16],float* r)
{
    float fTmp[7][4];

    // (ma11 + ma22) * (mb11 + mb22)
	multiply22(&fTmp[0][0], &fTmp[0][1], &fTmp[0][2], &fTmp[0][3],
					m[0][0] + m[0][10], m[0][1] + m[0][11], m[0][4] + m[0][14], m[0][5] + m[0][15],
					m[1][0] + m[1][10], m[1][1] + m[1][11], m[1][4] + m[1][14], m[1][5] + m[1][15]);

		// (ma21 + ma22) * mb11
	multiply22(&fTmp[1][0], &fTmp[1][1], &fTmp[1][2], &fTmp[1][3],
					m[0][8] + m[0][10], m[0][9] + m[0][11], m[0][12] + m[0][14], m[0][13] + m[0][15],
					m[1][0], m[1][1], m[1][4], m[1][5]);

	// ma11 * (mb12 - mb22)
	multiply22(&fTmp[2][0], &fTmp[2][1], &fTmp[2][2], &fTmp[2][3],
					m[0][0], m[0][1], m[0][4], m[0][5],
					m[1][2] - m[1][10], m[1][3] - m[1][11], m[1][6] - m[1][14], m[1][7] - m[1][15]);

	// ma22 * (mb21 - mb11)
	multiply22(&fTmp[3][0], &fTmp[3][1], &fTmp[3][2], &fTmp[3][3],
					m[0][10], m[0][11], m[0][14], m[0][15],
					m[1][8] - m[1][0], m[1][9] - m[1][1], m[1][12] - m[1][4], m[1][13] - m[1][5]);

	// (ma11 + ma12) * mb22
	multiply22(&fTmp[4][0], &fTmp[4][1], &fTmp[4][2], &fTmp[4][3],
					m[0][0] + m[0][2], m[0][1] + m[0][3], m[0][4] + m[0][6], m[0][5] + m[0][7],
					m[1][10], m[1][11], m[1][14], m[1][15]);

	// (ma21 - ma11) * (mb11 + mb12)
	multiply22(&fTmp[5][0], &fTmp[5][1], &fTmp[5][2], &fTmp[5][3],
					m[0][8] - m[0][0], m[0][9] - m[0][1], m[0][12] - m[0][4], m[0][13] - m[0][5],
					m[1][0] + m[1][2], m[1][1] + m[1][3], m[1][4] + m[1][6], m[1][5] + m[1][7]);

	// (ma12 - ma22) * (mb21 + mb22)
	multiply22(&fTmp[6][0], &fTmp[6][1], &fTmp[6][2], &fTmp[6][3],
					m[0][2] - m[0][10], m[0][3] - m[0][11], m[0][6] - m[0][14], m[0][7] - m[0][15],
					m[1][8] + m[1][10], m[1][9] + m[1][11], m[1][12] + m[1][14], m[1][13] + m[1][15]);
    // top left block
	r[0] = fTmp[0][0] + fTmp[3][0] - fTmp[4][0] + fTmp[6][0];
	r[1] = fTmp[0][1] + fTmp[3][1] - fTmp[4][1] + fTmp[6][1];
	r[4] = fTmp[0][2] + fTmp[3][2] - fTmp[4][2] + fTmp[6][2];
	r[5] = fTmp[0][3] + fTmp[3][3] - fTmp[4][3] + fTmp[6][3];

	// top right block
	r[2] = fTmp[2][0] + fTmp[4][0];
	r[3] = fTmp[2][1] + fTmp[4][1];
	r[6] = fTmp[2][2] + fTmp[4][2];
	r[7] = fTmp[2][3] + fTmp[4][3];

	// bottom left block
	r[8] = fTmp[1][0] + fTmp[3][0];
	r[9] = fTmp[1][1] + fTmp[3][1];
	r[12] = fTmp[1][2] + fTmp[3][2];
	r[13] = fTmp[1][3] + fTmp[3][3];

	// bottom right block
	r[10] = fTmp[0][0] - fTmp[1][0] + fTmp[2][0] + fTmp[5][0];
	r[11] = fTmp[0][1] - fTmp[1][1] + fTmp[2][1] + fTmp[5][1];
	r[14] = fTmp[0][2] - fTmp[1][2] + fTmp[2][2] + fTmp[5][2];
	r[15] = fTmp[0][3] - fTmp[1][3] + fTmp[2][3] + fTmp[5][3];
}

static void normal(float(* m)[16],float* r)
{
    gint ri = 0;
    gint rj = 0;
    gint rk = 0;
    for (; ri < 4; ri++) {
        for (rj = 0; rj < 4; rj++) {
            float result = 0;
            for (rk = 0; rk < 4; rk++) {
                float a = m[0][ri * 4 + rk];
                float b = m[1][rk * 4 + rj];
                float c = a * b;
                result += c;
            }
            r[ri * 4 + rj] = result;
        }
    }
}

static void calculate(GtkWidget *widget, gpointer data)
{
    float m[2][16];
    float r[16];

    getFirstTwoMatrice(m, data);


    GTimer * timer = g_timer_new( );
    g_timer_start(timer);
    normal(m,r);
    g_timer_stop(timer);
    gdouble elapsed1 =  g_timer_elapsed(timer,
                         NULL);
    char str[10];
    sprintf(str, "%.7g", elapsed1 );
    gtk_label_set_text(GTK_LABEL (((MATRICES*)data)->l[0]),str);

    g_timer_reset(timer);
    g_timer_start(timer);
    optimized(m,r);
    g_timer_stop(timer);
    gdouble elapsed2 =  g_timer_elapsed(timer,
                         NULL);
    sprintf(str, "%.7g", elapsed2 );
    gtk_label_set_text(GTK_LABEL (((MATRICES*)data)->l[1]),str);

    sprintf(str, "%.7g", elapsed1 - elapsed2 );
    gtk_label_set_text(GTK_LABEL (((MATRICES*)data)->l[2]),str);

    putResultIntoTable(r, data);

}

int main (int argc, char *argv[])
{
  GtkWidget *button = NULL;
  GtkWidget *label = NULL;
  GtkWidget *separator = NULL;
  GtkWidget *win = NULL;
  GtkWidget *vbox = NULL;
  GtkWidget *hbox = NULL;
  GtkWidget *vbox1 = NULL;
  GtkWidget *hbox1 = NULL;
  GtkWidget *table = NULL;
  MATRICES mats;

  /* Initialize GTK+ */
  g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, (GLogFunc) gtk_false, NULL);
  gtk_init (&argc, &argv);
  g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING, g_log_default_handler, NULL);

  /* Create the main window */
  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_container_set_border_width (GTK_CONTAINER (win), 8);
  gtk_window_set_title (GTK_WINDOW (win), "Matrix Energy Saver");
  gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
  gtk_widget_set_size_request( GTK_WIDGET (win), 1024, 256);
  gtk_widget_realize (win);
  g_signal_connect (win, "destroy", gtk_main_quit, NULL);

  /* Create a vertical FALSE */
  vbox = gtk_vbox_new (FALSE, 6);
  gtk_container_add (GTK_CONTAINER (win), vbox);
  gtk_widget_show (vbox);

  /* Create a horizontal box and put it into vertical box */
  hbox = gtk_hbox_new (FALSE, 20);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);
  /* Create tables in hbox */
  table = gtk_table_new (4, 4, TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 0);
  createIdentityMatrix(table, &mats, 0);
  gtk_widget_show (table);

  label = gtk_label_new( "X" );
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 20);

  table = gtk_table_new (4,4,TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 0);
  createIdentityMatrix(table, &mats, 1);
  gtk_widget_show (table);

  label = gtk_label_new( "=" );
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 20);

  table = gtk_table_new (4,4,TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), table, FALSE, FALSE, 0);
  createIdentityMatrix(table, &mats, 2);
  gtk_widget_show (table);

  /* Creates a separator */
  separator = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 5);
  gtk_widget_show (separator);

  /* Create a horizontal box and put it into vertical box */
  hbox = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 10);
  gtk_widget_show (hbox);

  vbox1 = gtk_vbox_new (TRUE, 6);
  gtk_box_pack_start (GTK_BOX (hbox), vbox1, FALSE, TRUE, 0);
  gtk_widget_show (vbox1);

  hbox1 = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, TRUE, 0);
  gtk_widget_show (hbox1);

  label = gtk_label_new( "NORMAL ALGORITHM COMSUMES" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  label = gtk_label_new( "0.0" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  mats.l[0] = label;
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  label = gtk_label_new( "ms" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  hbox1 = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, TRUE, 0);
  gtk_widget_show (hbox1);

  label = gtk_label_new( "OPTIMIZED ALGORITHM COMSUMES" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  label = gtk_label_new( "0.0" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  mats.l[1] = label;
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  label = gtk_label_new( "ms" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  hbox1 = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, TRUE, 0);
  gtk_widget_show (hbox1);

  label = gtk_label_new( "TIME SAVED =" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  label = gtk_label_new( "0.0" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  mats.l[2] = label;
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  label = gtk_label_new( "ms" );
  gtk_misc_set_alignment(GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (hbox1), label, FALSE, TRUE, 7);

  button = gtk_button_new_with_label ("CALCULATE");
  g_signal_connect (button, "clicked",
		      G_CALLBACK (calculate), (gpointer)&mats);
  gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);

  /* Enter the main loop */
  gtk_widget_show_all (win);
  gtk_main ();
  return 0;
}
